package cn.edu.hbpu.service.impl;

import cn.edu.hbpu.common.R;
import cn.edu.hbpu.dto.AdminUser;
import cn.edu.hbpu.dto.UserDto;
import cn.edu.hbpu.entity.sys.SysUser;
import cn.edu.hbpu.handler.CustomException;
import cn.edu.hbpu.mapper.SysUserMapper;
import cn.edu.hbpu.service.SysUserService;
import cn.edu.hbpu.utils.DateTimeFormat;
import cn.edu.hbpu.utils.JwtUtils;
import cn.edu.hbpu.utils.RedisCache;
import cn.hutool.core.map.MapUtil;
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.code.kaptcha.Producer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static cn.edu.hbpu.common.Constants.*;

/**
 * @author lcy
 * @date 2022/8/4 16:00
 * @description
 */
@Slf4j
@Service
public class SysUserServiceImpl implements SysUserService {

    /**
     * userMapper
     */
    @Autowired
    private SysUserMapper sysUserMapper;

    /**
     * 密码加密对象
     */
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 引入验证码生成器对象
     */
    @Autowired
    private Producer producer;

    /**
     * RedisCache：redis工具类
     */
    @Autowired
    private RedisCache redisCache;

    /**
     * 1. 获取AuthenticationManager对象
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    private void verifyCode(UserDto sysUser) {
        // 校验验证码
        // codeKey
        String codeKey = sysUser.getCodeKey();
        // code
        String code = sysUser.getCode();

        // 获取前端传来的codeKey
        if (Objects.isNull(codeKey)) {
            throw new CustomException("请传入codeKey");
        }
        if (Objects.isNull(code)) {
            throw new CustomException("请传入验证码");
        }
        // 根据codeKey从数据库中获取code值
        String codeFromRedis = redisCache.getCacheObject("adminUser:code:" + codeKey);
        // 获取不到说明该键值对已经过期
        if (Objects.isNull(codeFromRedis)) {
            throw new CustomException("验证码已过期");
        }
        // 如果两边验证码不同，说明传入的验证码错误
        if (!code.equalsIgnoreCase(codeFromRedis)) {
            // 验证码错误，则删除此时codeKey在redis中的验证码
            redisCache.deleteObject("adminUser:code:" + sysUser.getCodeKey());
            throw new CustomException("请输入正确的验证码");
        }
    }

    @Override
    public R<Map<Object, Object>> login(UserDto sysUser) {
        // 校验验证码
        verifyCode(sysUser);
        // 能继续往下走，说明以上步骤正确，且用户输入的验证码也正确

        // 2. 调用该对象的authenticate进行身份验证。该方法最终会调用实现了UserDetailsService的接口的loadUserByUsername方法
        // 2.1 创建UsernamePasswordAuthenticationToken对象，并传入用户名和密码。
        Authentication authentication = new UsernamePasswordAuthenticationToken(sysUser.getUserName(), sysUser.getPassword(), null);
        // 2.2 调用authenticate方法后会得到封装了loadUserByUsername方法返回值的Authentication对象
        Authentication authenticate = authenticationManager.authenticate(authentication);
        // 3. 判断用户是否认证成功
        if (Objects.isNull(authenticate)) {
            throw new CustomException("登录失败");
        }
        // 4. 认证成功获取用户信息
        AdminUser adminUser = (AdminUser) authenticate.getPrincipal();
        // 4.1 查看账号是否可用
        int status = adminUser.getSysUser().getStatus();
        if (status <= 0) {
            throw new CustomException("账号已锁定，请联系管理员");
        }
        // 4.2 获取用户id
        String sysUserId = adminUser.getSysUser().getSysUserId();
        // 4.3 将用户id加密为token令牌
        String jwt = JwtUtils.createJwt(sysUserId);
        // 4.4 登录成功后，删除验证码
        redisCache.deleteObject("adminUser:code:" + sysUser.getCodeKey());

        // 5. 根据用户id将用户信息写入redis
        redisCache.setCacheObject("adminUser:loginedUser:" + sysUserId, adminUser, ADMIN_EXPIRED_TIME, TimeUnit.HOURS);

        return new R<>(HttpStatus.HTTP_OK, "登录成功", MapUtil.builder().put("Authorization", jwt).build());
    }

    @Override
    public R<Map<Object, Object>> generateCode() throws IOException {
        // 验证码存入redis中的键值
        String codeKey = UUID.randomUUID().toString();
        // 验证码值
        String code = producer.createText();
        log.info("code = " + code);
        // 创建验证码图片
        BufferedImage image = producer.createImage(code);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "jpg", outputStream);
        // 将图片转为BASE64格式并返回
        String str = "data:image/jpeg;base64,";
        String base64Image = str + Base64Utils.encodeToString(outputStream.toByteArray());

        // 将验证码值存入redis并设置过期时间为 60s
        redisCache.setCacheObject("adminUser:code:" + codeKey, code, CODE_EXPIRED_TIME, TimeUnit.SECONDS);

        // 返回验证码
        return new R<>(HttpStatus.HTTP_OK, MapUtil.builder()
                .put("codeKey", codeKey)
                .put("base64Img", base64Image)
                .build());
    }

    @Override
    public R<Map<Object, Object>> logout() {
        // 1. 因为经过了校验过滤器，所以可以从SecurityContextHolder取出 adminUser
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        // 因为 /adminUser/logout已经存放在白名单，所以 authentication.getPrincipal() 的默认值为anonymousUser
        if ("anonymousUser".equals(authentication.getPrincipal())) {
            throw new CustomException("请传入token令牌");
        }
        AdminUser adminUser = (AdminUser) authentication.getPrincipal();

        // 2. 将用户信息从redis中清除
        String userId = adminUser.getSysUser().getSysUserId();
        redisCache.deleteObject("adminUser:loginedUser:" + userId);
        // 3. 返回结果
        return new R<>(HttpStatus.HTTP_OK, "退出成功");
    }

    @Override
    public R<SysUser> getUserInfo() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        AdminUser adminUser = (AdminUser) authentication.getPrincipal();
        if (Objects.isNull(adminUser)) {
            throw new RuntimeException("登录已失效，请重新登录");
        }
        SysUser sysUser = adminUser.getSysUser();
        return new R<>(HttpStatus.HTTP_OK, sysUser);
    }

    @Override
    public R<String> updatePwd(String sysUserId, String newPwd) {
        if (Objects.isNull(sysUserId) || Objects.isNull(newPwd)) {
            throw new CustomException("请传入用户id和密码");
        }
        // 1. 获得当前当前登录的用户id
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        AdminUser adminUser = (AdminUser) authentication.getPrincipal();
        String userId = adminUser.getSysUser().getSysUserId();

        // 2. 判断当当要修改的密码的用户是否是管理员，再判断当前登录的用户是否是管理员
        if (sysUserId.equals(SYSTEM_USER_ID) && !SYSTEM_USER_ID.equals(userId)) {
            throw new CustomException("不能修改管理员用户密码");
        }

        // 3. 加密新密码
        String newEncodePwd = passwordEncoder.encode(newPwd);
        // 4. 创建SysUser对象
        SysUser sysUser = new SysUser();
        // 5. 根据系统用户id更改密码
        sysUser.setSysUserId(sysUserId);
        sysUser.setPassword(newEncodePwd);
        // 6. 更改密码
        sysUserMapper.updateById(sysUser);


        // 7. 如果被修改密码用户id与当前登录的用户id一致，则修改密码成功后跳转回登录页面
        if (sysUserId.equals(userId)) {
            // 6.1 清空redis缓存
            redisCache.deleteObject("adminUser:loginedUser:" + userId);
            // 6.2 跳转到登录页面
            return new R<>(HttpStatus.HTTP_MOVED_TEMP, "请重新登录");
        }
        // 8. 返回结果
        return new R<>(HttpStatus.HTTP_OK, "密码修改成功");
    }

    @Override
    public R<String> changeStatus(String sysUserId, int status) {
        if (status != 1 && status != 0) {
            throw new CustomException("请传入正常的状态值");
        }
        if (Objects.isNull(sysUserId)) {
            throw new CustomException("请传入用户id");
        }
        // 1. 判断是否是删除自己
        // 1.1 从SecurityContextHolder中获取当前登录用户的id
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        AdminUser adminUser = (AdminUser) authentication.getPrincipal();
        String currentLoginUserId = adminUser.getSysUser().getSysUserId();
        // 1.2 不能删除系统管理员
        if (sysUserId.equals(SYSTEM_USER_ID)) {
            throw new CustomException("不能禁用系统管理员");
        }
        // 1.3 将当前登录用户的id和要删除用户的id进行对比，相同则不能删除自己
        if (sysUserId.equals(currentLoginUserId)) {
            throw new CustomException("不能禁用自己");
        }

        // 2. 查询用户
        SysUser sysUser = sysUserMapper.selectById(sysUserId);
        // 3. 设置要修改的状态
        sysUser.setStatus(status);
        // 4. 调用方法修改账号状态
        sysUserMapper.updateById(sysUser);
        // 5. 返回结果
        return new R<>(HttpStatus.HTTP_OK, "状态修改成功");
    }

    @Override
    public R<String> deleteUser(String sysUserId) {
        if (Objects.isNull(sysUserId)) {
            throw new CustomException("请传入用户id");
        }
        // 1. 判断是否是删除自己
        // 1.1 从SecurityContextHolder中获取当前登录用户的id
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        AdminUser adminUser = (AdminUser) authentication.getPrincipal();
        String userId = adminUser.getSysUser().getSysUserId();
        // 1.2 不能删除系统管理员
        if (sysUserId.equals(SYSTEM_USER_ID)) {
            throw new CustomException("不能删除系统管理员");
        }
        // 1.3 将当前登录用户的id和要删除用户的id进行对比，相同则不能删除自己
        if (sysUserId.equals(userId)) {
            throw new CustomException("不能删除自己");
        }

        // 2. 删除用户
        sysUserMapper.deleteById(sysUserId);
        // 3. 返回结果
        return new R<>(HttpStatus.HTTP_OK, "删除用户成功");
    }

    @Override
    public R<Page<SysUser>> pagingQuery(String searchField, String searchText, String startDateTime, String endDateTime, int page, int pageSize) {
        // 1. 非空验证
        if (Objects.isNull(searchField) || Objects.isNull(searchText)) {
            throw new CustomException("请传入搜索字段或文本");
        }
        LocalDateTime start = null;
        LocalDateTime end = null;
        // 2. 将日期进行转换
        if (!Objects.isNull(startDateTime) && !Objects.isNull(endDateTime)) {
            start = DateTimeFormat.parseTimeToUtc(startDateTime);
            end = DateTimeFormat.parseTimeToUtc(endDateTime);
        }
        // 3. 将searchField转为下划线
        searchField = StringUtils.camelToUnderline(searchField);

        // 4. 创建Page对象
        Page<SysUser> pageInfo = new Page<>(page, pageSize);

        // 5. 创建查询构造器
        QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();

        // 6. 判断searchField的字段类型
        if (searchField.isEmpty()) {
            // 6.1 搜索字段为空时
            // 6.1.1 分页查询所有数据
            sysUserMapper.selectPage(pageInfo, null);
        } else if ("create_time".equals(searchField) || "update_time".equals(searchField)) {
            // 6.2 当搜索字段为创建时间和更新时间时
            queryWrapper.ge(searchField, start).le(searchField, end);
            sysUserMapper.selectPage(pageInfo, queryWrapper);
        } else {
            // 6.3 当搜索字段为其它字段时
            if ("status".equals(searchField)) {
                searchText = "正常".equals(searchText) ? "1" : "0";
            }
            queryWrapper.like(searchField, searchText);
            sysUserMapper.selectPage(pageInfo, queryWrapper);
        }

        // 7. 查询角色信息并进行封装
        List<SysUser> sysUserList = pageInfo.getRecords();
        for (SysUser sysUser : sysUserList) {
            String sysUserId = sysUser.getSysUserId();
            List<String> roles = sysUserMapper.selectRolesById(sysUserId);
            sysUser.setRoles(roles);
        }

        // 8. 返回结果
        return new R<>(HttpStatus.HTTP_OK, pageInfo);
    }

}
